import {Bounds, IContext, ICreateNodeData, INodeData, Point} from "./interfaces.ts";
import {NodeWrapper} from "./NodeWrapper.tsx";
import {Stage} from "./Stage.ts";

export function getNodeImplFactory(ctx: IContext, nodeData: INodeData) {
  return ctx.options?.nodeImplFactoryMap?.[nodeData.nodeType]
}

export function createNodeImpl(ctx: IContext, nodeData: INodeData) {
  const nodeImplFactory = getNodeImplFactory(ctx, nodeData)
  return nodeImplFactory?.(nodeData)
}

export function restoreHierarchy(stage: Stage, nodeDataList: INodeData[]) {
  const nodes = [] as NodeWrapper[]
  for (const nodeData of nodeDataList) {
    let nodeWrapper = stage.$createNode(nodeData as ICreateNodeData)
    nodes.push(nodeWrapper!)
  }

  for (const node of nodes) {
    const {pid} = node.nodeData!
    if (!pid) {
      continue
    }
    const parentNode = nodes.find(n => n.id === pid)
    if (!parentNode) {
      console.warn(`lose parentNode[id:${pid}]`)
      continue
    }
    parentNode.addChild(node)
    node.$updateGlobalBounds()
  }
  //console.log(nodes)

  return nodes[0]
}

export function saveHierarchy(node: NodeWrapper) {
  const nodes = [] as INodeData[]
  eachNode(node, childNode => {
    const nodeData = deepClone(childNode.nodeData)
    nodeData.pid = childNode.parent!.id
    nodes.push(nodeData)
  })
  return nodes
}

export function deepClone(targetObj: any) {
  return JSON.parse(JSON.stringify(targetObj))
}

export function eachNode(node: NodeWrapper, iter: (childNode: NodeWrapper, index: number, parentNode?: NodeWrapper) => boolean | void, parentNode?: NodeWrapper): boolean | void {
  iter(node, parentNode?.children.indexOf(node) ?? -1, parentNode)
  let interrupted = false
  if (node.children && node.children.length > 0) {
    for (let i = 0; i < node.children.length; i++) {
      const childNode = node.children[i]
      if (eachNode(childNode, iter, node)) {
        interrupted = true
        break
      }
    }
  }
  if (interrupted) {
    return true
  }
}

export function applyBoundsToEl(bounds: Bounds, el: HTMLElement, borderWidth = 1, offset: Point = {} as Point) {
  const {x = 0, y = 0, w = 0, h = 0} = bounds
  const style = el.style
  style.left = (x + (offset.x ?? 0) - borderWidth) + 'px'
  style.top = (y + (offset.y ?? 0) - borderWidth) + 'px'
  style.width = Math.max(0, w) + 'px'
  style.height = Math.max(0, h) + 'px'
}

export function boundsContainsPoint(bounds: Bounds, point: Point) {
  const {x, y} = point
  const {x: bx, y: by, w: bw, h: bh} = bounds

  return x >= bx && x <= bx + bw && y >= by && y <= by + bh
}

export function combineBoundsArr(boundsArr: Bounds[]) {
  if (boundsArr.length === 1) {
    return boundsArr[0]
  }

  let left: number = Number.MAX_VALUE, top: number = Number.MAX_VALUE, right: number = 0, bottom: number = 0
  for (let i = 0; i < boundsArr.length; i++) {
    const bounds = boundsArr[i];
    const {x, y, w, h} = bounds
    if (x < left) {
      left = x
    }
    if (y < top) {
      top = y
    }
    if (right < x + w) {
      right = x + w
    }
    if (bottom < y + h) {
      bottom = y + h
    }
  }
  return {
    x: left,
    y: top,
    w: right - left,
    h: bottom - top,
  } as Bounds
}

export function isBoundsIntersect(boundsA: Bounds, boundsB: Bounds) {
  const {x: xA, y: yA, w: wA, h: hA} = boundsA
  const {x: xB, y: yB, w: wB, h: hB} = boundsB

  return xA <= xB + wB && xA + wA >= xB && yA <= yB + hB && yA + hA >= yB
}

export function makeUUID(root: NodeWrapper) {
  const ids: number[] = []
  eachNode(root, childNode => {
    ids.push(childNode.id)
  })
  return Math.max.apply(null, ids) + 1
}
